/*
 * network.c:
 *	Part of wiringPiD
 *	Copyright (c) 2012-2017 Gordon Henderson
 ***********************************************************************
 * This file is part of wiringPi:
 *	https://github.com/WiringPi/WiringPi/
 *
 *    wiringPi is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Lesser General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    wiringPi is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public License
 *    along with wiringPi.  If not, see <http://www.gnu.org/licenses/>.
 ***********************************************************************
 */

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdarg.h>
#include <malloc.h>

#include <fcntl.h>
#include <crypt.h>

#include "network.h"

#define	TRUE	(1==1)
#define	FALSE	(!TRUE)

// Local data

#define	SALT_LEN	16

static char salt [SALT_LEN + 1] ;
static char *returnedHash = NULL ;
static int serverFd = -1 ;

// Union for the server Socket Address

static union
{
  struct sockaddr_in  sin ;
  struct sockaddr_in6 sin6 ;
} serverSockAddr ; 

// and client address

static union
{
  struct sockaddr_in  sin ;
  struct sockaddr_in6 sin6 ;
} clientSockAddr ;


/*
 * getClientIP:
 *	Returns a pointer to a static string containing the clients IP address
 *********************************************************************************
 */

char *getClientIP (void)
{
  char buf [INET6_ADDRSTRLEN] ;
  static char ipAddress [1024] ;

  if (clientSockAddr.sin.sin_family == AF_INET)	// IPv4
  {
    if (snprintf (ipAddress, 1024, "IPv4: %s", 
	inet_ntop (clientSockAddr.sin.sin_family, (void *)&clientSockAddr.sin.sin_addr, buf, sizeof (buf))) == 1024)
      strcpy (ipAddress, "Too long") ;
  }
  else						// IPv6
  {
    if (clientSockAddr.sin.sin_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED (&clientSockAddr.sin6.sin6_addr))
    {
      if (snprintf (ipAddress, 1024, "IPv4in6: %s", 
	inet_ntop (clientSockAddr.sin.sin_family, (char *)&clientSockAddr.sin6.sin6_addr, buf, sizeof(buf))) == 1024)
      strcpy (ipAddress, "Too long") ;
    }
    else
    {
      if (snprintf (ipAddress, 1024, "IPv6: %s", 
	inet_ntop (clientSockAddr.sin.sin_family, (char *)&clientSockAddr.sin6.sin6_addr, buf, sizeof(buf))) == 1024)
      strcpy (ipAddress, "Too long") ;
    }
  }

  return ipAddress ;
}



/*
 * clientPstr: clientPrintf:
 *	Print over a network socket
 *********************************************************************************
 */

static int clientPstr (int fd, char *s)
{
  int len = strlen (s) ;
  return (write (fd, s, len) == len) ? 0 : -1 ;
}

static int clientPrintf (const int fd, const char *message, ...)
{
  va_list argp ;
  char buffer [1024] ;

  va_start (argp, message) ;
    vsnprintf (buffer, 1023, message, argp) ;
  va_end (argp) ;

  return clientPstr (fd, buffer) ;
}


/*
 * sendGreeting:
 *	Send some text to the client device
 *********************************************************************************
 */

int sendGreeting (int clientFd)
{
  if (clientPrintf (clientFd, "200 Welcome to wiringPiD - https://github.com/WiringPi/WiringPi/\n") < 0)
    return -1 ;

  return clientPrintf (clientFd, "200 Connecting from: %s\n", getClientIP ()) ;
}


/*
 * getSalt:
 *	Create a random 'salt' value for the password encryption process
 *********************************************************************************
 */

static int getSalt (char drySalt [])
{
  static const char *seaDog =	"abcdefghijklmnopqrstuvwxyz"
				"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
				"0123456789/." ;

  unsigned char wetSalt [SALT_LEN] ;
  int i, fd ;

  if ((fd = open ("/dev/urandom", O_RDONLY)) < 0)
    return fd ;

  if (read (fd, wetSalt, SALT_LEN) != SALT_LEN)
    return -1 ;

  close (fd) ;

  for (i = 0 ; i < SALT_LEN ; ++i)
    drySalt [i] = seaDog [wetSalt [i] & 63] ;
    
  drySalt [SALT_LEN] = 0 ;

  return 0 ;
}


/*
 * sendChallenge:
 *	Create and send our salt (aka nonce) to the remote device
 *********************************************************************************
 */

int sendChallenge (int clientFd)
{
  if (getSalt (salt) < 0)
    return -1 ;

  return clientPrintf (clientFd, "Challenge %s\n", salt) ;
}


/*
 * getResponse:
 *	Read the encrypted password from the remote device.
 *********************************************************************************
 */


int getResponse (int clientFd)
{
  char reply [1024] ;
  int len ;

// Being sort of lazy about this. I'm expecting an SHA-512 hash back and these
//	are exactly 86 characters long, so no reason not to, I guess...

  len = 86 ;

  if (setsockopt (clientFd, SOL_SOCKET, SO_RCVLOWAT, (void *)&len, sizeof (len)) < 0)
    return -1 ;

  len = recv (clientFd, reply, 86, 0) ;
  if (len != 86)
    return -1 ;

  reply [len] = 0 ;

  if ((returnedHash = malloc (len + 1)) == NULL)
    return -1 ;

  strcpy (returnedHash, reply) ;

  return 0 ;
}


/*
 * passwordMatch:
 *	See if there's a match. If not, we simply dump them.
 *********************************************************************************
 */

int passwordMatch (const char *password)
{
  char *encrypted ;
  char salted [1024] ;

  sprintf (salted, "$6$%s$", salt) ;

  encrypted = crypt (password, salted) ;

// 20: $6$ then 16 characters of salt, then $
// 86 is the length of an SHA-512 hash

  return strncmp (encrypted + 20, returnedHash, 86) == 0 ;
}


/* 
 * setupServer:
 *	Do what's needed to create a local server socket instance that can listen
 *	on both IPv4 and IPv6 interfaces.
 *********************************************************************************
 */

int setupServer (int serverPort)
{
  socklen_t clientSockAddrSize = sizeof (clientSockAddr) ;

  int on = 1 ;
  int family ;
  socklen_t serverSockAddrSize ;
  int clientFd ;

// Try to create an IPv6 socket

  serverFd = socket (PF_INET6, SOCK_STREAM, 0) ;

// If it didn't work, then fall-back to IPv4.

  if (serverFd < 0)
  {
    if ((serverFd = socket (PF_INET, SOCK_STREAM, 0)) < 0)
      return -1 ;

    family             = AF_INET ;
    serverSockAddrSize = sizeof (struct sockaddr_in) ;
  }
  else		// We got an IPv6 socket
  {
    family             = AF_INET6 ;
    serverSockAddrSize = sizeof (struct sockaddr_in6) ;
  }

  if (setsockopt (serverFd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (on)) < 0)
    return -1 ;

// Setup the servers socket address - cope with IPv4 and v6.

  memset (&serverSockAddr, 0, sizeof (serverSockAddr)) ;
  switch (family)
  {
    case AF_INET:
      serverSockAddr.sin.sin_family      = AF_INET ;
      serverSockAddr.sin.sin_addr.s_addr = htonl (INADDR_ANY) ;
      serverSockAddr.sin.sin_port        = htons (serverPort) ;
      break;

    case AF_INET6:
      serverSockAddr.sin6.sin6_family = AF_INET6 ;
      serverSockAddr.sin6.sin6_addr   = in6addr_any ;
      serverSockAddr.sin6.sin6_port   = htons (serverPort) ;
  }

// Bind, listen and accept

  if (bind (serverFd, (struct sockaddr *)&serverSockAddr, serverSockAddrSize) < 0)
    return -1 ;

  if (listen (serverFd, 4) < 0)	// Really only going to talk to one client at a time...
    return -1 ;

  if ((clientFd = accept (serverFd, (struct sockaddr *)&clientSockAddr, &clientSockAddrSize)) < 0)
    return -1 ;

  return clientFd ;
}


/*
 * closeServer:
 *********************************************************************************
 */

void closeServer (int clientFd)
{
  if (serverFd != -1) close (serverFd) ;
  if (clientFd != -1) close (clientFd) ;
  serverFd = clientFd = -1 ;
}
